#include "CNN.h"

#include <memory>
#include <cassert>

#include "../Utils/RandomUtil.h"

using namespace std;

CNN::CNN(int ViewSize, ConvLayerParam ConvLayers[])
{
	int index = 0;
	
	do
	{
		vector<ConvProcessor> ConvLayer;

		for (int n = 0; n < ConvLayers[index].FeatureCount; n++)
		{
			ConvProcessor convProcessor(ConvLayers[index].ConvWindowSize);
			ConvLayer.push_back(convProcessor);
		}

		this->m_ConvLayers.push_back(ConvLayer);

	} while (ConvLayers[++index].ConvWindowSize != 0);
}

Matrix2 CNN::Execute(Matrix2 & Input)
{
	vector< shared_ptr<Matrix2> > InputMatrix;
	vector< shared_ptr<Matrix2> > OutputMatrix;

	InputMatrix.push_back(make_shared<Matrix2>(Input));

	for (auto it_convlayer = this->m_ConvLayers.begin();
		it_convlayer != this->m_ConvLayers.end();
		++it_convlayer)
	{
		OutputMatrix.clear();

		for (auto it_inputMatrix = InputMatrix.begin();
			it_inputMatrix != InputMatrix.end();
			++it_inputMatrix)
		{
			for (auto it_convProcessor = it_convlayer->begin();
				it_convProcessor != it_convlayer->end();
				++it_convProcessor)
			{
				shared_ptr<Matrix2> output;

				it_convProcessor->Convolution(*it_inputMatrix, output);

				OutputMatrix.push_back(output);

			} // for it_convProcessor

		} // for it_inputMatrix
	
		InputMatrix = OutputMatrix;
	} // for it_convlayer

	int rl = OutputMatrix[0]->GetLineCount();
	int rc = OutputMatrix[0]->GetColumnCount() * OutputMatrix.size();

	Matrix2 Result(rc, rl);
	for (int n = 0; n < OutputMatrix.size(); n++)
	{
		for (int col = 0; col < OutputMatrix[n]->GetColumnCount(); col++)
		{
			for (int line = 0; line < OutputMatrix[n]->GetLineCount(); line++)
			{
				Result[line][col * n] = (*OutputMatrix[n])[line][col];
			}
		}
	}

	return Result;
}

double CNN::Sigmoid(double value)
{
	return ::tanh(2.0 * value / 3.0);

	// return (2.0 / (1.0 + ::exp(-1.0 * value))) - 1.0;
}

CNN::ConvProcessor::ConvProcessor(int nConvWindowSize) :
	ConvW(nConvWindowSize, nConvWindowSize)
{
	for (int n = 0; n < nConvWindowSize * nConvWindowSize; n++)
	{
		*(this->ConvW.GetData() + n) = RandomUtil::GenGaussianRandomNumber(0.0, 1.0);
	}
	this->ConvB = RandomUtil::GenGaussianRandomNumber(0.0, 1.0);

	this->SubSimpleW = RandomUtil::GenGaussianRandomNumber(0.0, 1.0);
	this->SubSimpleB = RandomUtil::GenGaussianRandomNumber(0.0, 1.0);
}

void CNN::ConvProcessor::Convolution(shared_ptr<Matrix2> & input, shared_ptr<Matrix2> & output)
{
	int ConvOutputMatrixLines = input->GetLineCount() - this->ConvW.GetLineCount() + 1;
	int ConvOutputMatrixCols = input->GetColumnCount() - this->ConvW.GetColumnCount() + 1;
	
	assert(ConvOutputMatrixLines % 2 == 0);
	assert(ConvOutputMatrixCols % 2 == 0);

	int SubSimpleOutputMatrixLines = ConvOutputMatrixLines / 2;
	int SubSimpleOutputMatrixCols = ConvOutputMatrixCols / 2;
	
	Matrix2 ConvOutputMatrix(ConvOutputMatrixCols, ConvOutputMatrixLines);
	output = make_shared<Matrix2>(SubSimpleOutputMatrixCols, SubSimpleOutputMatrixLines);

	double sum = 0;

	for (int in_line = 0; in_line < ConvOutputMatrixLines; in_line++)
	{
		for (int in_col = 0; in_col < ConvOutputMatrixCols; in_col++)
		{
			sum = 0;
			for (int conv_line = 0; conv_line < this->ConvW.GetLineCount(); conv_line++)
			{
				for (int conv_col = 0; conv_col < this->ConvW.GetColumnCount(); conv_col++)
				{
					sum += (*input)[in_line + conv_line][in_col + conv_col] * this->ConvW[conv_line][conv_col];
				}
			}
			sum += this->ConvB;

			ConvOutputMatrix[in_line][in_col] = CNN::Sigmoid(sum);
		}
	}

	for (int line = 0; line < ConvOutputMatrixLines; line += 2)
	{
		for (int col = 0; col < ConvOutputMatrixCols; col += 2)
		{
			sum = 0;
			for (int nl = 0; nl < 2; nl++)
			{
				for (int nc = 0; nc < 2; nc++)
				{
					sum += ConvOutputMatrix[line + nl][col + nc];
				}
			}
			sum *= this->SubSimpleW;
			sum += this->SubSimpleB;

			(*output)[line / 2][col / 2] = CNN::Sigmoid(sum);
		}
	}
}